﻿#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include "ngx_global.h"
#include "ngx_macro.h"
#include "ngx_func.h" 

/*自定义有关信号的结构体*/
typedef struct 
{
    int           signo;       /*信号对应的数字编号*/
    const  char   *signame;    /*信号对应的中文名字比如SIGHUP*/
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); /*信号处理函数的写法，里面的参数由操作系统决定，我们不用管*/
}ngx_signal_t;

/*声明一个信号处理函数*/
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); //static表示该函数只在当前文件内可见
static void ngx_process_get_status(void);                                      //获取子进程的结束状态，防止单独kill子进程时子进程变成僵尸进程

/*该数组定义了我们自己想要处理的信号，其它没有的就按默认方式处理
信号处理方式：
(1)忽略
(2)自定义处理方式
(3)按操作系统默认方式*/
ngx_signal_t  signals[] = {
    // signo      signame             handler
    { SIGHUP,    "SIGHUP",           ngx_signal_handler },        //终端断开信号
    { SIGINT,    "SIGINT",           ngx_signal_handler },        //Ctrl+C信号 
    { SIGTERM,   "SIGTERM",          ngx_signal_handler },        //标识15
    { SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        //子进程退出时父进程会收到这个信号(处理僵尸进程)
    { SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        //标识3
    { SIGIO,     "SIGIO",            ngx_signal_handler },        //指示一个异步I/O事件【通用异步I/O信号】
    { SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        //我们想忽略这个信号，SIGSYS表示收到了一个无效系统调用，如果我们不忽略，进程会被操作系统杀死，--标识31
                                                                  //所以我们把handler设置为NULL，代表 我要求忽略这个信号，请求操作系统不要执行缺省的该信号处理动作（杀掉我）
    //...日后根据需要再继续增加
    { 0,         NULL,               NULL               }         //信号对应的数字至少是1，所以可以用0作为一个特殊标记
};

/*
 * @file      ngx_signal.cxx
 * @function  信号初始化处理函数
 * @parameter void
 * @return    0成功，-1失败
 * @date      2021/07/19
*/
int ngx_init_signals()
{
    //指向自定义结构数组的指针 
    ngx_signal_t      *sig;
    //sigaction：系统定义的跟信号有关的一个结构，我们后续调用系统的sigaction()函数时要用到这个同名的结构
    struct sigaction   sa;

    //将signo == 0作为一个标记，因为信号的编号都不为0；
    for (sig = signals; sig->signo != 0; sig++)  
    {        
        //我们注意，现在要把一堆信息往 变量sa对应的结构里弄
        memset(&sa,0,sizeof(struct sigaction));
        //如果信号处理函数不为空，这当然表示我要定义自己的信号处理函数
        if(sig->handler)
        {
            /*信号处理函数*/
            sa.sa_sigaction = sig->handler;
            /*sa_flags：int型，指定信号的一些选项，(自定义信号处理函数的类型)
            设置了该标记(SA_SIGINFO)，就表示信号附带的参数可以被传递到信号处理函数中*/
            sa.sa_flags = SA_SIGINFO;
        }
        else
        {
            sa.sa_handler = SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员，表示忽略信号的处理程序，否则操作系统的缺省信号处理程序很可能把这个进程杀掉；
                                      //其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样， sa_sigaction带的参数多，信息量大，
                                       //而sa_handler带的参数少，信息量少；如果你想用sa_sigaction，那么你就需要把sa_flags设置为SA_SIGINFO；                                       
        } //end if

        sigemptyset(&sa.sa_mask);   //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号，那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理，这个sigaddset三章五节讲过；
                                    //这里.sa_mask是个信号集（描述信号的集合），用于表示要阻塞的信号，sigemptyset()这个函数咱们在第三章第五节讲过：把信号集中的所有信号清0，本意就是不准备阻塞任何信号；
                                    
        
        //设置信号处理动作(信号处理函数)，说白了这里就是让这个信号来了后调用我的处理程序，有个老的同类函数叫signal，不过signal这个函数被认为是不可靠信号语义，不建议使用，大家统一用sigaction
        if (sigaction(sig->signo, &sa, NULL) == -1) //参数1：要操作的信号
                                                     //参数2：主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容
                                                      //参数3：返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】，跟参数2同一个类型，我们这里不需要这个东西，所以直接设置为NULL；
        {   
            ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) failed",sig->signame); //显示到日志文件中去的 
            return -1; //有失败就直接返回
        }   
        else
        {            
            //ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志 
            //ngx_log_stderr(0,"sigaction(%s) succed!",sig->signame); //直接往屏幕上打印看看 ，不需要时可以去掉
        }
    } //end for
    return 0;
}

/*
 * @file      ngx_signal.cxx
 * @function  信号初处理函数
 * @parameter siginfo：这个系统定义的结构中包含了信号产生原因的有关信息
 * @return    0成功，-1失败
 * @date      2021/07/19
*/
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
    ngx_signal_t    *sig;
    /*用于记录一个动作字符串以往日志文件中写*/
    char            *action = (char *)"";  //目前还没有什么动作；;
    for(sig = signals; sig->signo != 0; sig++)
    {
        /*如果是相应的信号找到了，就退出进行下一步操作*/
        if (sig->signo == signo) 
        { 
            break;
        }
    }
    /*master进程处理的信号*/
    if(ngx_process == NGX_PROCESS_MASTER)
    {
        switch(signo)
        {
        case SIGCHLD:  /*子进程(worker进程)退出父进程(master进程)会收到该信号*/
            ngx_reap = 1;  //标记子进程状态变化，日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】
            break;
        //其他信号处理以后待增加
        default:
            break;
        }
    }
    /*worker进程处理的信号*/
    else if(ngx_process == NGX_PROCESS_WORKER)
    {
        //...目前还没有要处理的
    }
    else
    {
        //do nothing
    } 

    /*这里我们对触发的信号写日志，比如这个信号是什么
    信号，那个进程触发的，以及进程名等等，这些信息我们
    可以从函数的第二个参数获取，里面的信息由操作系统填充*/
    if(siginfo && siginfo->si_pid)
    {
        ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); 
    }
    else
    {
        ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received %s",signo, sig->signame, action);//没有发送该信号的进程id，所以不显示发送该信号的进程id
    }

    if(signo == SIGCHLD) 
    {
        ngx_process_get_status(); //获取子进程的结束状态
    }
    return;
}
/*
 * @file      ngx_signal.cxx
 * @function  获取子进程的结束状态，防止单独kill子进程时子进程变成僵尸进程
 * @parameter void
 * @return    void
 * @date      2021/07/19
*/
static void ngx_process_get_status(void)
{
    pid_t            pid;
    int              status;
    int              err;
    int              one=0; //抄自官方nginx，应该是标记信号正常处理过一次

    //当你杀死一个子进程时，父进程会收到这个SIGCHLD信号。
    for(;;)
    {
        pid = waitpid(-1, &status, WNOHANG); //第一个参数为-1，表示等待任何子进程，
                                              //第二个参数：保存子进程的状态信息(大家如果想详细了解，可以百度一下)。
                                               //第三个参数：提供额外选项，WNOHANG表示不要阻塞，让这个waitpid()立即返回        
        if(pid == 0) //子进程没结束，会立即返回这个数字，但这里应该不是这个数字【因为一般是子进程退出时会执行到这个函数】
        {
            return;
        }
        if(pid == -1)//这表示这个waitpid调用有错误，有错误也理解返回出去，我们管不了这么多
        {
            //这里处理代码抄自官方nginx，主要目的是打印一些日志。考虑到这些代码也许比较成熟，所以，就基本保持原样照抄吧；
            err = errno;
            if(err == EINTR)           //调用被某个信号中断
            {
                continue;
            }
            //没有子进程
            if(err == ECHILD  && one)
            {
                return;
            }
            //没有子进程
            if (err == ECHILD)
            {
                ngx_log_error_core(NGX_LOG_INFO,err,"waitpid() failed!");
                return;
            }
            ngx_log_error_core(NGX_LOG_ALERT,err,"waitpid() failed!");
            return;
        }
        //走到这里，表示  成功【返回进程id】 ，这里根据官方写法，打印一些日志来记录子进程的退出
        one = 1;  //标记waitpid()返回了正常的返回值
        if(WTERMSIG(status))  //获取使子进程终止的信号编号
        {
            ngx_log_error_core(NGX_LOG_ALERT,0,"pid = %P exited on signal %d!",pid,WTERMSIG(status)); //获取使子进程终止的信号编号
        }
        else
        {
            ngx_log_error_core(NGX_LOG_NOTICE,0,"pid = %P exited with code %d!",pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
        }
    }
    return;
}
